import { getLogger } from '../logger/index'
import { ensureRunDirectory } from '../logger/recorders'
import { HttpSession } from '../request/http-session'
import { TdabsFlows } from '../request/tdabs-flows'
import { getPageParser } from '../shared/page-parser'

export interface QueryByRefInput { refLast6: string }
/**
 * 按证件/执照查询的入参
 *
 * 约束：
 * - 必须同时提供：(hkid 或 passport) + license
 * - `hkid` 建议传入去除括号/空格后的大写字符串（形如 R7838198 或 R783819(A) → R783819A）
 * - `passport` 建议传入大写、去空格
 * - `license` 驾照号按官网规则原样传入
 */
export interface QueryByIdInput { passport?: string, hkid?: string, license?: string }
export interface UpdateTimeslotInput {
  refLast6: string
  officeCode?: string
  preferredDates?: string[]
  preferredTimes?: string[]
}

export class TdabsRequestService {
  private logger = getLogger()

  private createSession(): { session: HttpSession, flows: TdabsFlows } {
    const session = new HttpSession()
    const flows = new TdabsFlows(session)
    return { session, flows }
  }

  async queryByRef(input: QueryByRefInput): Promise<{ success: boolean, data?: any, error?: string }> {
    try {
      const overallStart = Date.now()
      this.logger.info('【TDABS】按参考号查询开始', input)
      // 初始化本次“request”运行目录（按参考后6位聚合）
      try { await ensureRunDirectory('request' as any, { id: input.refLast6, name: '查询' }, true) }
      catch {}
      const { flows } = this.createSession()
      const parser = getPageParser()

      const maxAttempts = 3
      let lastError: string | undefined
      let lastSnapshots: { start?: string | null, entry?: string | null, get_info?: string | null } = {}

      for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        const attemptStart = Date.now()
        this.logger.info('【TDABS】按参考号查询尝试', { ref: input.refLast6, attempt })

        const tStartOp = Date.now()
        const step1 = await flows.startOperation('E')
        const startOperationMs = Date.now() - tStartOp

        const tEntry = Date.now()
        const step2 = await flows.enterEntry('E')
        const enterEntryMs = Date.now() - tEntry

        const tGetInfo = Date.now()
        const page = await flows.getApptInfoByRef(step2.html, input.refLast6, 'E')
        const getInfoMs = Date.now() - tGetInfo

        lastSnapshots = {
          start: step1.html_saved || null,
          entry: step2.html_saved || null,
          get_info: page.html_saved || null,
        }

        const tDetect = Date.now()
        const errorInfo = parser.detectError(page.html)
        const detectErrorMs = Date.now() - tDetect
        if (errorInfo.success && errorInfo.data?.type === 'system_busy') {
          const attemptMs = Date.now() - attemptStart
          const stepPairs: Array<{ name: string, ms: number }> = [
            { name: 'startOperation', ms: startOperationMs },
            { name: 'enterEntry', ms: enterEntryMs },
            { name: 'getInfo', ms: getInfoMs },
            { name: 'detectError', ms: detectErrorMs },
          ]
          const longest = stepPairs.reduce((a, b) => (b.ms > a.ms ? b : a), { name: 'startOperation', ms: 0 })
          this.logger.warn(
            `【TDABS】系统繁忙，稍后重试 | 本次用时 ${attemptMs}ms，最慢步骤 ${longest.name}=${longest.ms}ms`,
            { ref: input.refLast6, attempt, error: errorInfo.data, attemptMs, steps: { startOperationMs, enterEntryMs, getInfoMs, detectErrorMs }, longest },
          )
          lastError = errorInfo.data.message || '系统繁忙'
          // 轻量退避：200~600ms 随机延迟
          const delay = 200 + Math.floor(Math.random() * 400)
          await new Promise(r => setTimeout(r, delay))
          continue
        }
        // 明确“未找到记录”的情况：直接返回失败，便于前端统一处理为空结果
        if (errorInfo.success && (String(errorInfo.data?.code).toUpperCase() === 'NO_RECORD')) {
          const attemptMs = Date.now() - attemptStart
          const totalMs = Date.now() - overallStart
          const stepPairs: Array<{ name: string, ms: number }> = [
            { name: 'startOperation', ms: startOperationMs },
            { name: 'enterEntry', ms: enterEntryMs },
            { name: 'getInfo', ms: getInfoMs },
            { name: 'detectError', ms: detectErrorMs },
          ]
          const longest = stepPairs.reduce((a, b) => (b.ms > a.ms ? b : a), { name: 'startOperation', ms: 0 })
          try {
            this.logger.info(
              `【TDABS】未查询到记录（NO_RECORD） | 总耗时 ${totalMs}ms，本次 ${attemptMs}ms，最慢步骤 ${longest.name}=${longest.ms}ms`,
              {
                ref: input.refLast6,
                message: errorInfo.data?.message,
                snapshots: lastSnapshots,
                attempt,
                attemptMs,
                totalMs,
                steps: { startOperationMs, enterEntryMs, getInfoMs, detectErrorMs },
                longest,
              },
            )
          }
          catch {}
          const msg = errorInfo.data?.message || 'NO_RECORD'
          this.logger.info('【TDABS】按参考号查询无记录', { ref: input.refLast6, attempt, message: msg })
          return {
            success: false,
            error: 'NO_RECORD',
            data: {
              html_saved: lastSnapshots,
              stats: {
                attempt,
                attemptMs,
                totalMs,
                steps: { startOperationMs, enterEntryMs, getInfoMs, detectErrorMs },
                longest,
              },
            },
          }
        }

        const tExtract = Date.now()
        const formFields = parser.extractFormFields(page.html, 'frmAppointment')
        const extractFormMs = Date.now() - tExtract

        const tParse = Date.now()
        const brief = parser.parseAppointmentInfo(page.html)
        const parseMs = Date.now() - tParse
        const attemptMs = Date.now() - attemptStart
        const totalMs = Date.now() - overallStart
        const stepPairs: Array<{ name: string, ms: number }> = [
          { name: 'startOperation', ms: startOperationMs },
          { name: 'enterEntry', ms: enterEntryMs },
          { name: 'getInfo', ms: getInfoMs },
          { name: 'detectError', ms: detectErrorMs },
          { name: 'parse', ms: parseMs },
          { name: 'extractForm', ms: extractFormMs },
        ]
        const longest = stepPairs.reduce((a, b) => (b.ms > a.ms ? b : a), { name: 'startOperation', ms: 0 })
        this.logger.info(
          `【TDABS】按参考号查询完成 | 总耗时 ${totalMs}ms，本次 ${attemptMs}ms，最慢步骤 ${longest.name}=${longest.ms}ms`,
          { ok: true, ref: input.refLast6, no_record: false, attempt, attemptMs, totalMs, steps: { startOperationMs, enterEntryMs, getInfoMs, detectErrorMs, parseMs, extractFormMs }, longest },
        )
        return {
          success: true,
          data: {
            url: page.url,
            fields: formFields.success ? formFields.data : {},
            parsed: brief.success ? brief.data : undefined,
            html_saved: lastSnapshots,
            saved_dir_hint: '开发环境：项目根目录 temp-html；生产环境：用户数据目录 temp-html（设置页可打开）',
            stats: { attempt, attemptMs, totalMs, steps: { startOperationMs, enterEntryMs, getInfoMs, detectErrorMs, parseMs, extractFormMs }, longest },
          },
        }
      }

      // 重试仍失败，返回错误并附带快照路径
      const totalMs = Date.now() - overallStart
      this.logger.warn('【TDABS】按参考号查询重试后仍失败', { ref: input.refLast6, lastError, totalMs })
      return {
        success: false,
        error: lastError || '查询失败',
        data: {
          html_saved: lastSnapshots,
          saved_dir_hint: '开发环境：项目根目录 temp-html；生产环境：用户数据目录 temp-html（设置页可打开）',
          stats: { totalMs },
        },
      }
    }
    catch (e) {
      const errMsg = e instanceof Error ? e.message : String(e)
      this.logger.warn('【TDABS】按参考号查询异常', { ref: input.refLast6, err: errMsg })
      return { success: false, error: errMsg }
    }
  }

  /**
   * 按“身份证/护照 + 驾照号”查询预约信息
   *
   * 流程：E 操作 → 进入入口页 → 提交到 cmdCheckIdentifierAction → 解析预约信息。
   * - 证件类型映射：HKID → H；Passport → P；驾照号填入 storedAppointmentLicenseNumber2。
   * - 错误识别：命中 NO_RECORD 时返回 { success:false, error:'NO_RECORD' }，前端据此展示“无结果”。
   * - 兜底：若首轮未解析出参考编号且表单含 `refNoSeq`（参考号后6位），将回退走一次“按参考号查询”，提高成功率。
   *
   * 返回：
   * - success=true：data.parsed（结构化预约信息）、data.fields（页面表单字段）、data.html_saved（快照路径，便于问题排查）。
   * - success=false 且 error='NO_RECORD'：无对应记录。
   */
  async queryById(input: QueryByIdInput): Promise<{ success: boolean, data?: any, error?: string }> {
    try {
      const overallStart = Date.now()
      this.logger.info('【TDABS】按证件/执照查询开始', input)
      if (!input.license || (!input.hkid && !input.passport))
        return { success: false, error: '按证件/执照查询需同时提供：(身份证或护照) + 驾照号码' }
      // 初始化本次“request”运行目录（按证件尾部或驾照尾部聚合）
      try {
        const tail = (input.license || input.hkid || input.passport || '').toString().slice(-4) || 'xxxx'
        await ensureRunDirectory('request' as any, { id: tail, name: '查询' }, true)
      }
      catch {}
      const { flows } = this.createSession()
      const parser = getPageParser()

      // 入参概要（去敏）
      const mask = (s?: string) => {
        if (!s)
          return ''
        const raw = String(s)
        if (raw.length <= 4)
          return `${raw[0] || ''}***${raw.slice(-1)}`
        return `${raw.slice(0, 1)}***${raw.slice(-2)}`
      }
      const docType = input.hkid ? 'HKID' : (input.passport ? 'PASSPORT' : 'UNKNOWN')
      this.logger.info('【TDABS】byId 入参概要', {
        docType,
        idMasked: mask(input.hkid || input.passport),
        licenseMasked: mask(input.license),
        idLen: String(input.hkid || input.passport || '').length,
        licenseLen: String(input.license || '').length,
      })

      const tStartOp = Date.now()
      const step1 = await flows.startOperation('E')
      const startOperationMs = Date.now() - tStartOp

      const tEntry = Date.now()
      const step2 = await flows.enterEntry('E')
      const enterEntryMs = Date.now() - tEntry

      const attemptStart = Date.now()
      const tGetInfo = Date.now()
      const page = await flows.getApptInfoByIdentity(step2.html, {
        passport: input.passport,
        hkid: input.hkid,
        license: input.license,
      })
      const getInfoMs = Date.now() - tGetInfo

      // 返回页类型
      try {
        const pageType = parser.identifyPageType(page.html, page.url)
        this.logger.info('【TDABS】byId 返回页概要', { url: page.url, pageType })
      }
      catch {}

      // 错误检测：系统繁忙 / 无记录（NO_RECORD）等
      const tDetect = Date.now()
      const errInfo = parser.detectError(page.html)
      const detectErrorMs = Date.now() - tDetect
      if (errInfo.success && String(errInfo.data?.code).toUpperCase() === 'NO_RECORD') {
        const attemptMs = Date.now() - attemptStart
        const totalMs = Date.now() - overallStart
        this.logger.info('【TDABS】按证件/执照查询无记录（NO_RECORD）', { attemptMs, totalMs })
        return {
          success: false,
          error: 'NO_RECORD',
          data: {
            html_saved: {
              start: step1.html_saved || null,
              entry: step2.html_saved || null,
              get_info: page.html_saved || null,
            },
            stats: { attempt: 1, attemptMs, totalMs, steps: { startOperationMs, enterEntryMs, getInfoMs, detectErrorMs } },
          },
        }
      }
      else if (errInfo.success && errInfo.data) {
        // 其他错误类型概要
        this.logger.warn('【TDABS】byId 检测到页面错误', { type: errInfo.data.type, code: errInfo.data.code, message: errInfo.data.message })
      }

      const tExtract = Date.now()
      const formFields = parser.extractFormFields(page.html, 'frmAppointment')
      const extractFormMs = Date.now() - tExtract

      const tParse = Date.now()
      const brief = parser.parseAppointmentInfo(page.html)
      const parseMs = Date.now() - tParse
      // Fallback：若未能解析出参考编号，且表单中含 refNoSeq（参考号后6位），则回退到“按参考号查询一次”
      let finalBrief = brief
      let finalPage = page
      if (!(brief.success && (brief.data as any)?.reference_number)) {
        const ref6 = (formFields.success && (formFields.data as any)?.refNoSeq) ? String((formFields.data as any).refNoSeq) : ''
        if (ref6 && ref6.length === 6) {
          this.logger.info('【TDABS】byId 解析缺失，尝试按参考后6位回退查询', { ref6 })
          const byRef = await flows.getApptInfoByRef(step2.html, ref6, 'E')
          finalPage = byRef
          const p2 = parser.parseAppointmentInfo(byRef.html)
          if (p2.success)
            finalBrief = p2
          else
            this.logger.warn('【TDABS】byId 回退查询未能解析出结果', { ref6 })
        }
        else {
          this.logger.warn('【TDABS】byId 页面未携带 refNoSeq，无法回退', { hasRefNoSeq: !!((formFields.success && (formFields.data as any)?.refNoSeq)) })
        }
      }
      const attemptMs = Date.now() - attemptStart
      const totalMs = Date.now() - overallStart
      const stepPairs: Array<{ name: string, ms: number }> = [
        { name: 'startOperation', ms: startOperationMs },
        { name: 'enterEntry', ms: enterEntryMs },
        { name: 'getInfo', ms: getInfoMs },
        { name: 'detectError', ms: detectErrorMs },
        { name: 'parse', ms: parseMs },
        { name: 'extractForm', ms: extractFormMs },
      ]
      const longest = stepPairs.reduce((a, b) => (b.ms > a.ms ? b : a), { name: 'startOperation', ms: 0 })
      // 解析概要（去敏）
      try {
        const parsedRef = (finalBrief.success && (finalBrief.data as any)?.reference_number) ? (finalBrief.data as any).reference_number : ''
        this.logger.info('【TDABS】byId 解析概要', {
          hasReference: !!parsedRef,
          referenceEnd6: parsedRef ? String(parsedRef).slice(-6) : '',
        })
      }
      catch {}

      this.logger.info(
        `【TDABS】按证件/执照查询完成 | 总耗时 ${totalMs}ms，本次 ${attemptMs}ms，最慢步骤 ${longest.name}=${longest.ms}ms`,
        { attempt: 1, attemptMs, totalMs, steps: { startOperationMs, enterEntryMs, getInfoMs, parseMs, extractFormMs }, longest },
      )
      return {
        success: true,
        data: {
          url: finalPage.url,
          fields: formFields.success ? formFields.data : {},
          parsed: finalBrief.success ? finalBrief.data : undefined,
          html_saved: {
            start: step1.html_saved || null,
            entry: step2.html_saved || null,
            get_info: finalPage.html_saved || page.html_saved || null,
          },
          saved_dir_hint: '开发环境：项目根目录 temp-html；生产环境：用户数据目录 temp-html（设置页可打开）',
          stats: { attempt: 1, attemptMs, totalMs, steps: { startOperationMs, enterEntryMs, getInfoMs, parseMs, extractFormMs }, longest },
        },
      }
    }
    catch (e) {
      const errMsg = e instanceof Error ? e.message : String(e)
      this.logger.warn('【TDABS】按证件/执照查询异常', { err: errMsg })
      return { success: false, error: errMsg }
    }
  }

  async cancelByRef(input: QueryByRefInput): Promise<{ success: boolean, message?: string, error?: string }> {
    try {
      this.logger.info('【TDABS】取消预约开始', input)
      try { await ensureRunDirectory('request' as any, { id: input.refLast6, name: '取消' }, true) }
      catch {}
      const { flows } = this.createSession()
      await flows.startOperation('U')
      const entry = await flows.enterEntry('U')

      const parser = getPageParser()
      // 按文档只使用 DI 服务类型
      const page = await flows.getApptInfoByRef(entry.html, input.refLast6, 'U', 'DI')
      const errInfo = parser.detectError(page.html)
      if (errInfo.success && errInfo.data?.type === 'system_busy') {
        await new Promise(r => setTimeout(r, 300))
        // 轻微退避后再次用 DI 重试一次
        const retryPage = await flows.getApptInfoByRef(entry.html, input.refLast6, 'U', 'DI')
        const retryErr = parser.detectError(retryPage.html)
        if (retryErr.success && retryErr.data?.type === 'system_busy')
          return { success: false, error: '系统繁忙，请稍后再试' }
        const brief2 = parser.parseAppointmentInfo(retryPage.html)
        if (!(brief2.success && (brief2.data as any)?.reference_number)) {
          // 兜底：重新进入入口后再取一次详情
          const reEntry = await flows.enterEntry('U')
          const rePage = await flows.getApptInfoByRef(reEntry.html, input.refLast6, 'U', 'DI')
          const reBrief = parser.parseAppointmentInfo(rePage.html)
          if (!(reBrief.success && (reBrief.data as any)?.reference_number))
            return { success: false, error: '未能获取预约详情，请稍后再试' }
          const reResult = await flows.cancelFromApptInfo(rePage.html)
          if (reResult.success)
            return { success: true, message: reResult.message }
          return { success: false, error: reResult.message || '取消失败' }
        }
        const result2 = await flows.cancelFromApptInfo(retryPage.html)
        if (result2.success)
          return { success: true, message: result2.message }
        return { success: false, error: result2.message || '取消失败' }
      }
      const brief = parser.parseAppointmentInfo(page.html)
      if (!(brief.success && (brief.data as any)?.reference_number)) {
        // 兜底：重新进入入口后再取一次详情
        const reEntry = await flows.enterEntry('U')
        const rePage = await flows.getApptInfoByRef(reEntry.html, input.refLast6, 'U', 'DI')
        const reBrief = parser.parseAppointmentInfo(rePage.html)
        if (!(reBrief.success && (reBrief.data as any)?.reference_number))
          return { success: false, error: '未能获取预约详情，请稍后再试' }
        const reResult = await flows.cancelFromApptInfo(rePage.html)
        if (reResult.success)
          return { success: true, message: reResult.message }
        return { success: false, error: reResult.message || '取消失败' }
      }
      const apptHtml = page.html

      const result = await flows.cancelFromApptInfo(apptHtml)
      if (result.success) {
        this.logger.info('【TDABS】取消预约成功', { ref: input.refLast6 })
        return { success: true, message: result.message }
      }
      this.logger.warn('【TDABS】取消预约失败（服务端）', { ref: input.refLast6, message: result.message })
      return { success: false, error: result.message || '取消失败' }
    }
    catch (e) {
      const errMsg = e instanceof Error ? e.message : String(e)
      this.logger.warn('【TDABS】取消预约异常', { ref: input.refLast6, err: errMsg })
      return { success: false, error: errMsg }
    }
  }

  async updateTimeslot(input: UpdateTimeslotInput): Promise<{ success: boolean, referenceNumber?: string, error?: string }> {
    try {
      try { await ensureRunDirectory('request' as any, { id: input.refLast6, name: '更改' }, true) }
      catch {}
      const { flows } = this.createSession()
      await flows.startOperation('U')
      const entry = await flows.enterEntry('U')
      const appt = await flows.getApptInfoByRef(entry.html, input.refLast6, 'U')
      const result = await flows.updateTimeslotFromApptInfo(appt.html, {
        officeCode: input.officeCode,
        preferredDates: input.preferredDates,
        preferredTimes: input.preferredTimes,
      })
      if (result.success)
        return { success: true, referenceNumber: result.reference }
      return { success: false, error: result.error || '更改失败' }
    }
    catch (e) {
      const msg = e instanceof Error ? e.message : String(e)
      return { success: false, error: msg }
    }
  }

  async batchCancelByRefs(
    refs: string[],
    options?: {
      concurrency?: number
      retries?: number
      retryDelayMs?: number
      verify?: boolean
      verifyDelayMs?: number
    },
  ): Promise<{
    success: boolean
    total: number
    ok: number
    fail: number
    errors: Array<{ ref: string, error: string, attempts?: number }>
    warns?: Array<{ ref: string, warning: string }>
  }> {
    const concurrency = Math.max(1, Math.min(5, Number(options?.concurrency) || 2))
    const maxRetries = Math.max(0, Math.min(5, Number(options?.retries) ?? 2))
    const baseDelay = Math.max(100, Math.min(5000, Number(options?.retryDelayMs) || 400))
    const doVerify = options?.verify !== false // 默认启用验证
    const verifyDelay = Math.max(0, Math.min(5000, Number(options?.verifyDelayMs) || 400))

    let ok = 0
    let fail = 0
    const errors: Array<{ ref: string, error: string, attempts?: number }> = []
    const warns: Array<{ ref: string, warning: string }> = []

    const sleep = (ms: number) => new Promise(r => setTimeout(r, ms))
    const isTransient = (msg?: string) => {
      if (!msg)
        return false
      const m = String(msg)
      // 将常见的瞬时/解析失败类错误纳入可重试
      return /系统繁忙|超时|timeout|网络|ECONN|EAI_AGAIN|暂时|重试|未能获取预约详情|未找到表单|找不到表单|no\s*form|取消失败/i.test(m)
    }

    const cancelWithRetry = async (ref: string): Promise<{ success: boolean, attempts: number, lastError?: string }> => {
      let attempts = 0
      let lastError: string | undefined
      for (let i = 0; i <= maxRetries; i++) {
        attempts = i + 1
        try {
          // 随机抖动，缓解突刺
          const jitter = Math.floor(Math.random() * 120)
          if (jitter)
            await sleep(jitter)
          const r = await this.cancelByRef({ refLast6: ref })
          if (r.success)
            return { success: true, attempts }
          lastError = r.error || '取消失败'
          if (!isTransient(lastError))
            break
        }
        catch (e: any) {
          lastError = e?.message || String(e)
          if (!isTransient(lastError))
            break
        }
        const delay = baseDelay * 1.6 ** i + Math.floor(Math.random() * 200)
        await sleep(delay)
      }
      return { success: false, attempts, lastError }
    }

    const queue = [...refs]
    const workers: Promise<void>[] = []
    for (let i = 0; i < concurrency; i++) {
      workers.push((async () => {
        while (queue.length > 0) {
          const ref = queue.shift() as string
          try {
            const r = await cancelWithRetry(ref)
            if (!r.success) {
              fail++
              errors.push({ ref, error: r.lastError || '取消失败', attempts: r.attempts })
              continue
            }

            // 取消成功后可选验证（轻量）
            if (doVerify) {
              try {
                if (verifyDelay)
                  await sleep(verifyDelay)
                const q = await this.queryByRef({ refLast6: ref })
                // 若查询明确返回仍存在预约信息，给出提醒，但不回滚成功计数
                if (q?.success && q?.data?.parsed?.reference_number) {
                  warns.push({ ref, warning: '取消成功但验证发现记录仍可查询，可能为远端延迟' })
                }
              }
              catch (e: any) {
                // 验证阶段失败仅记录提醒
                warns.push({ ref, warning: `取消成功但验证失败: ${e?.message || String(e)}` })
              }
            }

            ok++
          }
          catch (e) {
            fail++
            errors.push({ ref, error: e instanceof Error ? e.message : String(e) })
          }
        }
      })())
    }
    await Promise.all(workers)
    return { success: fail === 0, total: refs.length, ok, fail, errors, warns: warns.length ? warns : undefined }
  }
}

let svc: TdabsRequestService | null = null
export function getTdabsRequestService(): TdabsRequestService {
  if (!svc)
    svc = new TdabsRequestService()
  return svc
}
